package com.jxmobi.util;

import com.jxmobi.util.function.Op;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 *  calculate the time it consume to finish a certain task
 *
 * @author Xiaofei Chen <a href="mailto:xchen@jxmobi.com">Email the author</a>
 * @version 1.0 4/1/2016
 */
public abstract class TimeUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(TimeUtils.class);

    private static final double ONE_BILLION = 1_000_000_000;

    private TimeUtils() {}

    /**
     *  生成当前系统时间的 unix time 表示
     * @return 长度为4的 unix 时间 byte 数组
     */
    public static byte[] generateUnixTime() {
        return generateUnixTime(System.currentTimeMillis());
    }

    /**
     *  将给定的时间转换为 unix time 表示
     * @param time 给定的时间
     * @return 长度为4的字节数组
     */
    public static byte[] generateUnixTime(Date time) {
        return generateUnixTime(time.getTime());
    }

    /**
     *  将给定的时间转换为 unix time 表示
     * @param time 给定的时间
     * @return 长度为4的字节数组
     */
    public static byte[] generateUnixTime(long time) {
        int unixTime = (int) (time / 1000);
        return ByteBuffer.allocate(4).putInt(unixTime).array();
    }

    /**
     *  将给定的unix时间转换为 java 时间
     * @param bytes 长度为4的字节数组
     * @return
     */
    public static int unixTime2JavaTime(byte[] bytes) {
        if (bytes == null || bytes.length != 4) {
            throw new IllegalArgumentException("make sure the size of byte array equal 4");
        }

        return ByteBuffer.wrap(bytes).getInt();
    }

    /**
     *  将给定的unix时间转换为 java 时间 Date
     * @param bytes 长度为4的字节数组
     * @return
     */
    public static Date unixTime2JavaDate(byte[] bytes) {
        if (bytes == null || bytes.length != 4) {
            throw new IllegalArgumentException("make sure the size of byte array equal 4");
        }

        return new Date(ByteBuffer.wrap(bytes).getInt());
    }


    /**
     *  do some stuff, and then printf how long it take to accomplish such task
     * @param operation operation you gonna to make
     */
    public static void timeOpConsumed(Op operation) {
        long startTime = System.nanoTime();

        operation.runOp();

        long endTime = System.nanoTime();
        double elapsedSeconds = (endTime - startTime) / ONE_BILLION;
        LOGGER.debug(String.format(", Elapsed time:  %.3f seconds. ", elapsedSeconds));
    }

    public static void timeOpConsumed(Op operation, String message) {
        long startTime = System.nanoTime();

        operation.runOp();

        long endTime = System.nanoTime();
        double elapsedSeconds = (endTime - startTime) / ONE_BILLION;
        LOGGER.debug(String.format(message + ", Elapsed time:  %.3f seconds. ", elapsedSeconds));
    }

    /**
     *   execute a given task, and calculate the time it takes, and return the computing result
     * @param task task to be executed
     * @param <T> computing result type
     * @return computed result
     */
    public static <T> T timeOp(Callable<T> task) {
        T result = null;

        long startTime = System.nanoTime();

        try {
            result = task.call();
        } catch (Exception e) {
            e.printStackTrace();
        }

        long endTime = System.nanoTime();
        double elapsedSeconds = (endTime - startTime) / ONE_BILLION;
        LOGGER.debug(String.format(", Elapsed time:  %.3f seconds. ", elapsedSeconds));

        return result;
    }


    /**
     *  execute a given task, and calculate the time it takes, and return the computing result
     * @param task task to be executed
     * @param supplier prompt message provider when the job is done
     * @param <T> computing result type
     * @return computed result
     */
    public static <T> T timeOp(Callable<T> task, Supplier<String> supplier) {
        T result = null;

        long startTime = System.nanoTime();

        try {
            result = task.call();
        } catch (Exception e) {
            e.printStackTrace();
        }

        long endTime = System.nanoTime();
        double elapsedSeconds = (endTime - startTime) / ONE_BILLION;
        LOGGER.debug(String.format("%s , Elapsed time:  %.3f seconds. ", supplier.get(),  elapsedSeconds));

        return result;
    }

    /**
     *  execute a given task, and calculate the time it takes, and return the computing result
     * @param task task to be executed
     * @param message prompt message when the job is done
     * @param <T> computing result type
     * @return computed result
     */
    public static <T> T timeOp(Callable<T> task, String message) {
        T result = null;

        long startTime = System.nanoTime();

        try {
            result = task.call();
        } catch (Exception e) {
            e.printStackTrace();
        }

        long endTime = System.nanoTime();
        double elapsedSeconds = (endTime - startTime) / ONE_BILLION;
        LOGGER.debug(String.format("%s , Elapsed time:  %.3f seconds. ", message,  elapsedSeconds));

        return result;
    }

    public static <T, U, R> R timeOp2(T t, U u, BiFunction<T, U, R> task, String message) {
        R result = null;

        long startTime = System.nanoTime();

        try {
            result = task.apply(t, u);
        } catch (Exception e) {
            e.printStackTrace();
        }

        long endTime = System.nanoTime();
        double elapsedSeconds = (endTime - startTime) / ONE_BILLION;
        LOGGER.debug(String.format("%s , Elapsed time:  %.3f seconds. ", message,  elapsedSeconds));

        return result;
    }

}
